#include "process.h"

#include "bitmap.h"
#include "console.h"
#include "debug.h"
#include "global.h"
#include "memory.h"
#include "stdint.h"
#include "string.h"
#include "thread.h"
#include "tss.h"

extern void intr_exit();

void process_execute(void* filename, char* thread_name)
{

  // Allocate memory for the new thread.
  struct task_struct* thread = kernel_page_alloc(1);

  // Initialize the thread with the provided attributes.
  init_thread_PCB_info(thread, thread_name, default_prio);

  // Create the thread with the specified entry point and argument.
  init_thread_stack_func_info(thread, start_process, filename);

  create_user_vaddr_bitmap(thread);
  thread->pg_dir_entry = init_user_page_dir_entry();

  // Initializes memory block descriptors for arena
  block_desc_init(thread->u_block_desc);

  // Ensure the thread is not already in the ready queue.
  ASSERT(!elem_find(&thread_ready_list, &thread->ready_list_tag));
  // Add the thread to the ready queue.
  list_append(&thread_ready_list, &thread->ready_list_tag);

  // Ensure the thread is not already in the all thread queue.
  ASSERT(!elem_find(&thread_all_list, &thread->all_list_tag));
  // Add the thread to the all thread queue.
  list_append(&thread_all_list, &thread->all_list_tag);
}

void create_user_vaddr_bitmap(struct task_struct* user_prog)
{
  struct virtual_addr* prog_vaddr = &user_prog->usrprog_vaddr;
  prog_vaddr->vaddr_start = USER_VADDR_START;

  prog_vaddr->vaddr_bitmap.bitmap_bytes_lens = USR_VADDR_BITMAP_BYTE_LEN;
  uint32_t bitmap_pg_cnt = DIV_CEILING(USR_VADDR_BITMAP_BYTE_LEN, PG_SIZE);

  // The virtual address bitmap for user processes is located in kernel space
  // to prevent unauthorized changes by users.
  prog_vaddr->vaddr_bitmap.bits = kernel_page_alloc(bitmap_pg_cnt);
  bitmap_init(&prog_vaddr->vaddr_bitmap);
}

uint32_t* init_user_page_dir_entry()
{
  uint32_t* PDE_vaddr = kernel_page_alloc(1);
  if (PDE_vaddr == NULL)
  {
    console_put_str("create_page_dir: kernel_page_alloc failed!");
    return NULL;
  }

  // copy 256 PDE(page dictionary entry) started from #768 to #1023, which
  // belong to kernel space, to new process's PDE
  memcpy((uint32_t*)((uint32_t)PDE_vaddr + 0x300 * 4),
         (uint32_t*)(0xfffff000 + 0x300 * 4), 1024);

  // update PDE address of new process
  uint32_t new_page_dir_phy_addr = addr_v2p((uint32_t)PDE_vaddr);

  PDE_vaddr[1023] = new_page_dir_phy_addr | PG_US_U | PG_RW_W | PG_P_1;

  return PDE_vaddr;
}

void start_process(void* filename_)
{
  // Assign filename_ to function, the address of the function to execute
  void* function = filename_;

  // Get the PCB (Process Control Block) pointer of the current thread
  struct task_struct* cur = cur_thread_PCB_get();

  // Get address of process interrupt stack
  struct intr_stack* proc_intr_stack =
      (struct intr_stack*)((uint32_t)cur->self_kstack +
                           sizeof(struct thread_stack));

  // Initialize registers and other fields in the process stack
  proc_intr_stack->edi = proc_intr_stack->esi = proc_intr_stack->ebp =
      proc_intr_stack->esp_dummy = 0;
  proc_intr_stack->ebx = proc_intr_stack->edx = proc_intr_stack->ecx =
      proc_intr_stack->eax = 0;
  proc_intr_stack->gs =
      0; // We usually do not need gs in user mode, so set to 0
  proc_intr_stack->ds = proc_intr_stack->es = proc_intr_stack->fs =
      SELECTOR_U_DATA;
  proc_intr_stack->eip = function; // Set the address of the function to execute
  proc_intr_stack->cs = SELECTOR_U_CODE;
  proc_intr_stack->eflags =
      (EFLAGS_IOPL_0 | EFLAGS_MBS | EFLAGS_IF_1); // Set eflags in user mode

  // Initialize the stack top position, allocate a physical page for virtual
  // address 0xc0000000 - 0x1000, and set virtual address + 4096 as the stack
  // top
  proc_intr_stack->esp =
      (void*)((uint32_t)physical_page_alloc(user_pflag, USER_STACK3_VADDR) +
              PG_SIZE);
  proc_intr_stack->ss = SELECTOR_U_DATA;

  // Simulate interrupt exit to switch to user mode (Ring 3) for execution
  asm volatile("movl %0, %%esp; jmp intr_exit"
               :
               : "g"(proc_intr_stack)
               : "memory");
}

void pg_dir_entry_activate(struct task_struct* p_thread)
{
  // By default, use the physical address of the kernel's page directory entry,
  // which is used by kernel threads.
  uint32_t PDE_phy_addr = 0x100000;

  // If p_thread's page directory is not NULL, indicating it's a process,
  // get the physical address of the process's page directory for loading the
  // page table.
  if (p_thread->pg_dir_entry != NULL)
  {
    PDE_phy_addr = addr_v2p((uint32_t)p_thread->pg_dir_entry);
  }

  // Update the page directory register (CR3) to make the new page table
  // effective.
  asm volatile("movl %0,%%cr3" : : "r"(PDE_phy_addr) : "memory");
}

void process_activate(struct task_struct* p_thread)
{
  ASSERT(p_thread != NULL);

  // Activate the page directory for the process thread
  pg_dir_entry_activate(p_thread);

  /* Kernel threads have privilege level 0 by default.
   * The processor doesn't fetch the privilege level 0 stack address from TSS
   * during interrupts. Hence, there's no need to update esp0 for kernel
   * threads.
   */
  if (p_thread->pg_dir_entry)
    {
    // Update esp0 for this process thread to preserve context during
    // interrupts
    tss_esp_update(p_thread);
    }
}
